package io.gitee.itxinmeng.encrypt.spring.boot.aspect;

import cn.hutool.core.util.StrUtil;
import io.gitee.itxinmeng.encrypt.spring.boot.annotation.Decrypt;
import io.gitee.itxinmeng.encrypt.spring.boot.autoconfigure.properties.EncryptProperties;
import io.gitee.itxinmeng.encrypt.spring.boot.common.BodyHttpInputMessage;
import io.gitee.itxinmeng.encrypt.spring.boot.common.encrypt.EncryptMethodFactory;
import io.gitee.itxinmeng.encrypt.spring.boot.common.encrypt.strategy.IEncryptStrategy;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Objects;

/**
 * 对 requestBody进行加密
 *
 * @author mx
 */
@ControllerAdvice
public class ReqBodyAdvice implements RequestBodyAdvice {
    @Resource
    private EncryptProperties config;

    @Override
    public boolean supports(MethodParameter methodParam, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        //If the method has Encrypt Annotation
        boolean isDecrypt = methodParam.getMethod().isAnnotationPresent(Decrypt.class);
        if(!isDecrypt){
            return Boolean.FALSE;
        }
        Decrypt annotation = methodParam.getMethod().getAnnotation(Decrypt.class);
        String method = StrUtil.isNotBlank(annotation.method())? annotation.method() : config.getMethod();
        if(StrUtil.isBlank(method)){
            return Boolean.FALSE;
        }
        IEncryptStrategy encryptStrategy = EncryptMethodFactory.INSTANCE.getEncryptStrategy(method);
        return encryptStrategy.check();
    }

    /**
     * 实现 beforeBodyRead 方法，在read body之前先进行解密
     * Invoked second before the request body is read and converted.
     *
     * @param inputMessage  the request
     * @param parameter     the target method parameter
     * @param targetType    the target type, not necessarily the same as the method
     *                      parameter type, e.g. for {@code HttpEntity<String>}.
     * @param converterType the converter used to deserialize the body
     * @return a new instance 【decrypted object 】 (never {@code null})
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        Decrypt annotation = parameter.getMethod().getAnnotation(Decrypt.class);
        return new BodyHttpInputMessage(inputMessage, config, annotation.method());
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}